/*
 * Copyright (c) 1999, 2012, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package javax.swing;

import sun.awt.EmbeddedFrame;
import sun.awt.OSInfo;

import java.applet.Applet;
import java.awt.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.security.AccessController;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static javax.swing.ClientPropertyKey.PopupFactory_FORCE_HEAVYWEIGHT_POPUP;

/**
 * <code>PopupFactory</code>, as the name implies, is used to obtain
 * instances of <code>Popup</code>s. <code>Popup</code>s are used to
 * display a <code>Component</code> above all other <code>Component</code>s
 * in a particular containment hierarchy. The general contract is that
 * once you have obtained a <code>Popup</code> from a
 * <code>PopupFactory</code>, you must invoke <code>hide</code> on the
 * <code>Popup</code>. The typical usage is:
 * <pre>
 *   PopupFactory factory = PopupFactory.getSharedInstance();
 *   Popup popup = factory.getPopup(owner, contents, x, y);
 *   popup.show();
 *   ...
 *   popup.hide();
 * </pre>
 *
 * @see Popup
 * @since 1.4
 */
public class PopupFactory {

  /**
   * The shared instanceof <code>PopupFactory</code> is per
   * <code>AppContext</code>. This is the key used in the
   * <code>AppContext</code> to locate the <code>PopupFactory</code>.
   */
  private static final Object SharedInstanceKey =
      new StringBuffer("PopupFactory.SharedInstanceKey");

  /**
   * Max number of items to store in any one particular cache.
   */
  private static final int MAX_CACHE_SIZE = 5;

  /**
   * Key used to indicate a light weight popup should be used.
   */
  static final int LIGHT_WEIGHT_POPUP = 0;

  /**
   * Key used to indicate a medium weight Popup should be used.
   */
  static final int MEDIUM_WEIGHT_POPUP = 1;

  /*
   * Key used to indicate a heavy weight Popup should be used.
   */
  static final int HEAVY_WEIGHT_POPUP = 2;

  /**
   * Default type of Popup to create.
   */
  private int popupType = LIGHT_WEIGHT_POPUP;


  /**
   * Sets the <code>PopupFactory</code> that will be used to obtain
   * <code>Popup</code>s.
   * This will throw an <code>IllegalArgumentException</code> if
   * <code>factory</code> is null.
   *
   * @param factory Shared PopupFactory
   * @throws IllegalArgumentException if <code>factory</code> is null
   * @see #getPopup
   */
  public static void setSharedInstance(PopupFactory factory) {
    if (factory == null) {
      throw new IllegalArgumentException("PopupFactory can not be null");
    }
    SwingUtilities.appContextPut(SharedInstanceKey, factory);
  }

  /**
   * Returns the shared <code>PopupFactory</code> which can be used
   * to obtain <code>Popup</code>s.
   *
   * @return Shared PopupFactory
   */
  public static PopupFactory getSharedInstance() {
    PopupFactory factory = (PopupFactory) SwingUtilities.appContextGet(
        SharedInstanceKey);

    if (factory == null) {
      factory = new PopupFactory();
      setSharedInstance(factory);
    }
    return factory;
  }


  /**
   * Provides a hint as to the type of <code>Popup</code> that should
   * be created.
   */
  void setPopupType(int type) {
    popupType = type;
  }

  /**
   * Returns the preferred type of Popup to create.
   */
  int getPopupType() {
    return popupType;
  }

  /**
   * Creates a <code>Popup</code> for the Component <code>owner</code>
   * containing the Component <code>contents</code>. <code>owner</code>
   * is used to determine which <code>Window</code> the new
   * <code>Popup</code> will parent the <code>Component</code> the
   * <code>Popup</code> creates to. A null <code>owner</code> implies there
   * is no valid parent. <code>x</code> and
   * <code>y</code> specify the preferred initial location to place
   * the <code>Popup</code> at. Based on screen size, or other paramaters,
   * the <code>Popup</code> may not display at <code>x</code> and
   * <code>y</code>.
   *
   * @param owner Component mouse coordinates are relative to, may be null
   * @param contents Contents of the Popup
   * @param x Initial x screen coordinate
   * @param y Initial y screen coordinate
   * @return Popup containing Contents
   * @throws IllegalArgumentException if contents is null
   */
  public Popup getPopup(Component owner, Component contents,
      int x, int y) throws IllegalArgumentException {
    if (contents == null) {
      throw new IllegalArgumentException(
          "Popup.getPopup must be passed non-null contents");
    }

    int popupType = getPopupType(owner, contents, x, y);
    Popup popup = getPopup(owner, contents, x, y, popupType);

    if (popup == null) {
      // Didn't fit, force to heavy.
      popup = getPopup(owner, contents, x, y, HEAVY_WEIGHT_POPUP);
    }
    return popup;
  }

  /**
   * Returns the popup type to use for the specified parameters.
   */
  private int getPopupType(Component owner, Component contents,
      int ownerX, int ownerY) {
    int popupType = getPopupType();

    if (owner == null || invokerInHeavyWeightPopup(owner)) {
      popupType = HEAVY_WEIGHT_POPUP;
    } else if (popupType == LIGHT_WEIGHT_POPUP &&
        !(contents instanceof JToolTip) &&
        !(contents instanceof JPopupMenu)) {
      popupType = MEDIUM_WEIGHT_POPUP;
    }

    // Check if the parent component is an option pane.  If so we need to
    // force a heavy weight popup in order to have event dispatching work
    // correctly.
    Component c = owner;
    while (c != null) {
      if (c instanceof JComponent) {
        if (((JComponent) c).getClientProperty(
            PopupFactory_FORCE_HEAVYWEIGHT_POPUP) == Boolean.TRUE) {
          popupType = HEAVY_WEIGHT_POPUP;
          break;
        }
      }
      c = c.getParent();
    }

    return popupType;
  }

  /**
   * Obtains the appropriate <code>Popup</code> based on
   * <code>popupType</code>.
   */
  private Popup getPopup(Component owner, Component contents,
      int ownerX, int ownerY, int popupType) {
    if (GraphicsEnvironment.isHeadless()) {
      return getHeadlessPopup(owner, contents, ownerX, ownerY);
    }

    switch (popupType) {
      case LIGHT_WEIGHT_POPUP:
        return getLightWeightPopup(owner, contents, ownerX, ownerY);
      case MEDIUM_WEIGHT_POPUP:
        return getMediumWeightPopup(owner, contents, ownerX, ownerY);
      case HEAVY_WEIGHT_POPUP:
        Popup popup = getHeavyWeightPopup(owner, contents, ownerX, ownerY);
        if ((AccessController.doPrivileged(OSInfo.getOSTypeAction()) ==
            OSInfo.OSType.MACOSX) && (owner != null) &&
            (EmbeddedFrame.getAppletIfAncestorOf(owner) != null)) {
          ((HeavyWeightPopup) popup).setCacheEnabled(false);
        }
        return popup;
    }
    return null;
  }

  /**
   * Creates a headless popup
   */
  private Popup getHeadlessPopup(Component owner, Component contents,
      int ownerX, int ownerY) {
    return HeadlessPopup.getHeadlessPopup(owner, contents, ownerX, ownerY);
  }

  /**
   * Creates a light weight popup.
   */
  private Popup getLightWeightPopup(Component owner, Component contents,
      int ownerX, int ownerY) {
    return LightWeightPopup.getLightWeightPopup(owner, contents, ownerX,
        ownerY);
  }

  /**
   * Creates a medium weight popup.
   */
  private Popup getMediumWeightPopup(Component owner, Component contents,
      int ownerX, int ownerY) {
    return MediumWeightPopup.getMediumWeightPopup(owner, contents,
        ownerX, ownerY);
  }

  /**
   * Creates a heavy weight popup.
   */
  private Popup getHeavyWeightPopup(Component owner, Component contents,
      int ownerX, int ownerY) {
    if (GraphicsEnvironment.isHeadless()) {
      return getMediumWeightPopup(owner, contents, ownerX, ownerY);
    }
    return HeavyWeightPopup.getHeavyWeightPopup(owner, contents, ownerX,
        ownerY);
  }

  /**
   * Returns true if the Component <code>i</code> inside a heavy weight
   * <code>Popup</code>.
   */
  private boolean invokerInHeavyWeightPopup(Component i) {
    if (i != null) {
      Container parent;
      for (parent = i.getParent(); parent != null; parent =
          parent.getParent()) {
        if (parent instanceof Popup.HeavyWeightWindow) {
          return true;
        }
      }
    }
    return false;
  }


  /**
   * Popup implementation that uses a Window as the popup.
   */
  private static class HeavyWeightPopup extends Popup {

    private static final Object heavyWeightPopupCacheKey =
        new StringBuffer("PopupFactory.heavyWeightPopupCache");

    private volatile boolean isCacheEnabled = true;

    /**
     * Returns either a new or recycled <code>Popup</code> containing
     * the specified children.
     */
    static Popup getHeavyWeightPopup(Component owner, Component contents,
        int ownerX, int ownerY) {
      Window window = (owner != null) ? SwingUtilities.
          getWindowAncestor(owner) : null;
      HeavyWeightPopup popup = null;

      if (window != null) {
        popup = getRecycledHeavyWeightPopup(window);
      }

      boolean focusPopup = false;
      if (contents != null && contents.isFocusable()) {
        if (contents instanceof JPopupMenu) {
          JPopupMenu jpm = (JPopupMenu) contents;
          Component popComps[] = jpm.getComponents();
          for (Component popComp : popComps) {
            if (!(popComp instanceof MenuElement) &&
                !(popComp instanceof JSeparator)) {
              focusPopup = true;
              break;
            }
          }
        }
      }

      if (popup == null ||
          ((JWindow) popup.getComponent())
              .getFocusableWindowState() != focusPopup) {

        if (popup != null) {
          // The recycled popup can't serve us well
          // dispose it and create new one
          popup._dispose();
        }

        popup = new HeavyWeightPopup();
      }

      popup.reset(owner, contents, ownerX, ownerY);

      if (focusPopup) {
        JWindow wnd = (JWindow) popup.getComponent();
        wnd.setFocusableWindowState(true);
        // Set window name. We need this in BasicPopupMenuUI
        // to identify focusable popup window.
        wnd.setName("###focusableSwingPopup###");
      }

      return popup;
    }

    /**
     * Returns a previously disposed heavy weight <code>Popup</code>
     * associated with <code>window</code>. This will return null if
     * there is no <code>HeavyWeightPopup</code> associated with
     * <code>window</code>.
     */
    private static HeavyWeightPopup getRecycledHeavyWeightPopup(Window w) {
      synchronized (HeavyWeightPopup.class) {
        List<HeavyWeightPopup> cache;
        Map<Window, List<HeavyWeightPopup>> heavyPopupCache = getHeavyWeightPopupCache();

        if (heavyPopupCache.containsKey(w)) {
          cache = heavyPopupCache.get(w);
        } else {
          return null;
        }
        if (cache.size() > 0) {
          HeavyWeightPopup r = cache.get(0);
          cache.remove(0);
          return r;
        }
        return null;
      }
    }

    /**
     * Returns the cache to use for heavy weight popups. Maps from
     * <code>Window</code> to a <code>List</code> of
     * <code>HeavyWeightPopup</code>s.
     */
    private static Map<Window, List<HeavyWeightPopup>> getHeavyWeightPopupCache() {
      synchronized (HeavyWeightPopup.class) {
        Map<Window, List<HeavyWeightPopup>> cache = (Map<Window, List<HeavyWeightPopup>>) SwingUtilities
            .appContextGet(
                heavyWeightPopupCacheKey);

        if (cache == null) {
          cache = new HashMap<Window, List<HeavyWeightPopup>>(2);
          SwingUtilities.appContextPut(heavyWeightPopupCacheKey,
              cache);
        }
        return cache;
      }
    }

    /**
     * Recycles the passed in <code>HeavyWeightPopup</code>.
     */
    private static void recycleHeavyWeightPopup(HeavyWeightPopup popup) {
      synchronized (HeavyWeightPopup.class) {
        List<HeavyWeightPopup> cache;
        Window window = SwingUtilities.getWindowAncestor(
            popup.getComponent());
        Map<Window, List<HeavyWeightPopup>> heavyPopupCache = getHeavyWeightPopupCache();

        if (window instanceof Popup.DefaultFrame ||
            !window.isVisible()) {
          // If the Window isn't visible, we don't cache it as we
          // likely won't ever get a windowClosed event to clean up.
          // We also don't cache DefaultFrames as this indicates
          // there wasn't a valid Window parent, and thus we don't
          // know when to clean up.
          popup._dispose();
          return;
        } else if (heavyPopupCache.containsKey(window)) {
          cache = heavyPopupCache.get(window);
        } else {
          cache = new ArrayList<HeavyWeightPopup>();
          heavyPopupCache.put(window, cache);
          // Clean up if the Window is closed
          final Window w = window;

          w.addWindowListener(new WindowAdapter() {
            public void windowClosed(WindowEvent e) {
              List<HeavyWeightPopup> popups;

              synchronized (HeavyWeightPopup.class) {
                Map<Window, List<HeavyWeightPopup>> heavyPopupCache2 =
                    getHeavyWeightPopupCache();

                popups = heavyPopupCache2.remove(w);
              }
              if (popups != null) {
                for (int counter = popups.size() - 1;
                    counter >= 0; counter--) {
                  popups.get(counter)._dispose();
                }
              }
            }
          });
        }

        if (cache.size() < MAX_CACHE_SIZE) {
          cache.add(popup);
        } else {
          popup._dispose();
        }
      }
    }

    /**
     * Enables or disables cache for current object.
     */
    void setCacheEnabled(boolean enable) {
      isCacheEnabled = enable;
    }

    //
    // Popup methods
    //
    public void hide() {
      super.hide();
      if (isCacheEnabled) {
        recycleHeavyWeightPopup(this);
      } else {
        this._dispose();
      }
    }

    /**
     * As we recycle the <code>Window</code>, we don't want to dispose it,
     * thus this method does nothing, instead use <code>_dipose</code>
     * which will handle the disposing.
     */
    void dispose() {
    }

    void _dispose() {
      super.dispose();
    }
  }


  /**
   * ContainerPopup consolidates the common code used in the light/medium
   * weight implementations of <code>Popup</code>.
   */
  private static class ContainerPopup extends Popup {

    /**
     * Component we are to be added to.
     */
    Component owner;
    /**
     * Desired x location.
     */
    int x;
    /**
     * Desired y location.
     */
    int y;

    public void hide() {
      Component component = getComponent();

      if (component != null) {
        Container parent = component.getParent();

        if (parent != null) {
          Rectangle bounds = component.getBounds();

          parent.remove(component);
          parent.repaint(bounds.x, bounds.y, bounds.width,
              bounds.height);
        }
      }
      owner = null;
    }

    public void pack() {
      Component component = getComponent();

      if (component != null) {
        component.setSize(component.getPreferredSize());
      }
    }

    void reset(Component owner, Component contents, int ownerX,
        int ownerY) {
      if ((owner instanceof JFrame) || (owner instanceof JDialog) ||
          (owner instanceof JWindow)) {
        // Force the content to be added to the layered pane, otherwise
        // we'll get an exception when adding to the RootPaneContainer.
        owner = ((RootPaneContainer) owner).getLayeredPane();
      }
      super.reset(owner, contents, ownerX, ownerY);

      x = ownerX;
      y = ownerY;
      this.owner = owner;
    }

    boolean overlappedByOwnedWindow() {
      Component component = getComponent();
      if (owner != null && component != null) {
        Window w = SwingUtilities.getWindowAncestor(owner);
        if (w == null) {
          return false;
        }
        Window[] ownedWindows = w.getOwnedWindows();
        if (ownedWindows != null) {
          Rectangle bnd = component.getBounds();
          for (Window window : ownedWindows) {
            if (window.isVisible() &&
                bnd.intersects(window.getBounds())) {

              return true;
            }
          }
        }
      }
      return false;
    }

    /**
     * Returns true if popup can fit the screen and the owner's top parent.
     * It determines can popup be lightweight or mediumweight.
     */
    boolean fitsOnScreen() {
      boolean result = false;
      Component component = getComponent();
      if (owner != null && component != null) {
        int popupWidth = component.getWidth();
        int popupHeight = component.getHeight();

        Container parent = (Container) SwingUtilities.getRoot(owner);
        if (parent instanceof JFrame ||
            parent instanceof JDialog ||
            parent instanceof JWindow) {

          Rectangle parentBounds = parent.getBounds();
          Insets i = parent.getInsets();
          parentBounds.x += i.left;
          parentBounds.y += i.top;
          parentBounds.width -= i.left + i.right;
          parentBounds.height -= i.top + i.bottom;

          if (JPopupMenu.canPopupOverlapTaskBar()) {
            GraphicsConfiguration gc =
                parent.getGraphicsConfiguration();
            Rectangle popupArea = getContainerPopupArea(gc);
            result = parentBounds.intersection(popupArea)
                .contains(x, y, popupWidth, popupHeight);
          } else {
            result = parentBounds
                .contains(x, y, popupWidth, popupHeight);
          }
        } else if (parent instanceof JApplet) {
          Rectangle parentBounds = parent.getBounds();
          Point p = parent.getLocationOnScreen();
          parentBounds.x = p.x;
          parentBounds.y = p.y;
          result = parentBounds.contains(x, y, popupWidth, popupHeight);
        }
      }
      return result;
    }

    Rectangle getContainerPopupArea(GraphicsConfiguration gc) {
      Rectangle screenBounds;
      Toolkit toolkit = Toolkit.getDefaultToolkit();
      Insets insets;
      if (gc != null) {
        // If we have GraphicsConfiguration use it
        // to get screen bounds
        screenBounds = gc.getBounds();
        insets = toolkit.getScreenInsets(gc);
      } else {
        // If we don't have GraphicsConfiguration use primary screen
        screenBounds = new Rectangle(toolkit.getScreenSize());
        insets = new Insets(0, 0, 0, 0);
      }
      // Take insets into account
      screenBounds.x += insets.left;
      screenBounds.y += insets.top;
      screenBounds.width -= (insets.left + insets.right);
      screenBounds.height -= (insets.top + insets.bottom);
      return screenBounds;
    }
  }


  /**
   * Popup implementation that is used in headless environment.
   */
  private static class HeadlessPopup extends ContainerPopup {

    static Popup getHeadlessPopup(Component owner, Component contents,
        int ownerX, int ownerY) {
      HeadlessPopup popup = new HeadlessPopup();
      popup.reset(owner, contents, ownerX, ownerY);
      return popup;
    }

    Component createComponent(Component owner) {
      return new Panel(new BorderLayout());
    }

    public void show() {
    }

    public void hide() {
    }
  }


  /**
   * Popup implementation that uses a JPanel as the popup.
   */
  private static class LightWeightPopup extends ContainerPopup {

    private static final Object lightWeightPopupCacheKey =
        new StringBuffer("PopupFactory.lightPopupCache");

    /**
     * Returns a light weight <code>Popup</code> implementation. If
     * the <code>Popup</code> needs more space that in available in
     * <code>owner</code>, this will return null.
     */
    static Popup getLightWeightPopup(Component owner, Component contents,
        int ownerX, int ownerY) {
      LightWeightPopup popup = getRecycledLightWeightPopup();

      if (popup == null) {
        popup = new LightWeightPopup();
      }
      popup.reset(owner, contents, ownerX, ownerY);
      if (!popup.fitsOnScreen() ||
          popup.overlappedByOwnedWindow()) {
        popup.hide();
        return null;
      }
      return popup;
    }

    /**
     * Returns the cache to use for heavy weight popups.
     */
    private static List<LightWeightPopup> getLightWeightPopupCache() {
      List<LightWeightPopup> cache = (List<LightWeightPopup>) SwingUtilities.appContextGet(
          lightWeightPopupCacheKey);
      if (cache == null) {
        cache = new ArrayList<LightWeightPopup>();
        SwingUtilities.appContextPut(lightWeightPopupCacheKey, cache);
      }
      return cache;
    }

    /**
     * Recycles the LightWeightPopup <code>popup</code>.
     */
    private static void recycleLightWeightPopup(LightWeightPopup popup) {
      synchronized (LightWeightPopup.class) {
        List<LightWeightPopup> lightPopupCache = getLightWeightPopupCache();
        if (lightPopupCache.size() < MAX_CACHE_SIZE) {
          lightPopupCache.add(popup);
        }
      }
    }

    /**
     * Returns a previously used <code>LightWeightPopup</code>, or null
     * if none of the popups have been recycled.
     */
    private static LightWeightPopup getRecycledLightWeightPopup() {
      synchronized (LightWeightPopup.class) {
        List<LightWeightPopup> lightPopupCache = getLightWeightPopupCache();
        if (lightPopupCache.size() > 0) {
          LightWeightPopup r = lightPopupCache.get(0);
          lightPopupCache.remove(0);
          return r;
        }
        return null;
      }
    }


    //
    // Popup methods
    //
    public void hide() {
      super.hide();

      Container component = (Container) getComponent();

      component.removeAll();
      recycleLightWeightPopup(this);
    }

    public void show() {
      Container parent = null;

      if (owner != null) {
        parent = (owner instanceof Container ? (Container) owner : owner.getParent());
      }

      // Try to find a JLayeredPane and Window to add
      for (Container p = parent; p != null; p = p.getParent()) {
        if (p instanceof JRootPane) {
          if (p.getParent() instanceof JInternalFrame) {
            continue;
          }
          parent = ((JRootPane) p).getLayeredPane();
          // Continue, so that if there is a higher JRootPane, we'll
          // pick it up.
        } else if (p instanceof Window) {
          if (parent == null) {
            parent = p;
          }
          break;
        } else if (p instanceof JApplet) {
          // Painting code stops at Applets, we don't want
          // to add to a Component above an Applet otherwise
          // you'll never see it painted.
          break;
        }
      }

      Point p = SwingUtilities.convertScreenLocationToParent(parent, x,
          y);
      Component component = getComponent();

      component.setLocation(p.x, p.y);
      if (parent instanceof JLayeredPane) {
        parent.add(component, JLayeredPane.POPUP_LAYER, 0);
      } else {
        parent.add(component);
      }
    }

    Component createComponent(Component owner) {
      JComponent component = new JPanel(new BorderLayout(), true);

      component.setOpaque(true);
      return component;
    }

    //
    // Local methods
    //

    /**
     * Resets the <code>Popup</code> to an initial state.
     */
    void reset(Component owner, Component contents, int ownerX,
        int ownerY) {
      super.reset(owner, contents, ownerX, ownerY);

      JComponent component = (JComponent) getComponent();

      component.setOpaque(contents.isOpaque());
      component.setLocation(ownerX, ownerY);
      component.add(contents, BorderLayout.CENTER);
      contents.invalidate();
      pack();
    }
  }


  /**
   * Popup implementation that uses a Panel as the popup.
   */
  private static class MediumWeightPopup extends ContainerPopup {

    private static final Object mediumWeightPopupCacheKey =
        new StringBuffer("PopupFactory.mediumPopupCache");

    /**
     * Child of the panel. The contents are added to this.
     */
    private JRootPane rootPane;


    /**
     * Returns a medium weight <code>Popup</code> implementation. If
     * the <code>Popup</code> needs more space that in available in
     * <code>owner</code>, this will return null.
     */
    static Popup getMediumWeightPopup(Component owner, Component contents,
        int ownerX, int ownerY) {
      MediumWeightPopup popup = getRecycledMediumWeightPopup();

      if (popup == null) {
        popup = new MediumWeightPopup();
      }
      popup.reset(owner, contents, ownerX, ownerY);
      if (!popup.fitsOnScreen() ||
          popup.overlappedByOwnedWindow()) {
        popup.hide();
        return null;
      }
      return popup;
    }

    /**
     * Returns the cache to use for medium weight popups.
     */
    private static List<MediumWeightPopup> getMediumWeightPopupCache() {
      List<MediumWeightPopup> cache = (List<MediumWeightPopup>) SwingUtilities.appContextGet(
          mediumWeightPopupCacheKey);

      if (cache == null) {
        cache = new ArrayList<MediumWeightPopup>();
        SwingUtilities.appContextPut(mediumWeightPopupCacheKey, cache);
      }
      return cache;
    }

    /**
     * Recycles the MediumWeightPopup <code>popup</code>.
     */
    private static void recycleMediumWeightPopup(MediumWeightPopup popup) {
      synchronized (MediumWeightPopup.class) {
        List<MediumWeightPopup> mediumPopupCache = getMediumWeightPopupCache();
        if (mediumPopupCache.size() < MAX_CACHE_SIZE) {
          mediumPopupCache.add(popup);
        }
      }
    }

    /**
     * Returns a previously used <code>MediumWeightPopup</code>, or null
     * if none of the popups have been recycled.
     */
    private static MediumWeightPopup getRecycledMediumWeightPopup() {
      synchronized (MediumWeightPopup.class) {
        List<MediumWeightPopup> mediumPopupCache = getMediumWeightPopupCache();
        if (mediumPopupCache.size() > 0) {
          MediumWeightPopup r = mediumPopupCache.get(0);
          mediumPopupCache.remove(0);
          return r;
        }
        return null;
      }
    }

    //
    // Popup
    //

    public void hide() {
      super.hide();
      rootPane.getContentPane().removeAll();
      recycleMediumWeightPopup(this);
    }

    public void show() {
      Component component = getComponent();
      Container parent = null;

      if (owner != null) {
        parent = owner.getParent();
      }
            /*
              Find the top level window,
              if it has a layered pane,
              add to that, otherwise
              add to the window. */
      while (!(parent instanceof Window || parent instanceof Applet) &&
          (parent != null)) {
        parent = parent.getParent();
      }
      // Set the visibility to false before adding to workaround a
      // bug in Solaris in which the Popup gets added at the wrong
      // location, which will result in a mouseExit, which will then
      // result in the ToolTip being removed.
      if (parent instanceof RootPaneContainer) {
        parent = ((RootPaneContainer) parent).getLayeredPane();
        Point p = SwingUtilities.convertScreenLocationToParent(parent,
            x, y);
        component.setVisible(false);
        component.setLocation(p.x, p.y);
        parent.add(component, JLayeredPane.POPUP_LAYER,
            0);
      } else {
        Point p = SwingUtilities.convertScreenLocationToParent(parent,
            x, y);

        component.setLocation(p.x, p.y);
        component.setVisible(false);
        parent.add(component);
      }
      component.setVisible(true);
    }

    Component createComponent(Component owner) {
      Panel component = new MediumWeightComponent();

      rootPane = new JRootPane();
      // NOTE: this uses setOpaque vs LookAndFeel.installProperty as
      // there is NO reason for the RootPane not to be opaque. For
      // painting to work the contentPane must be opaque, therefor the
      // RootPane can also be opaque.
      rootPane.setOpaque(true);
      component.add(rootPane, BorderLayout.CENTER);
      return component;
    }

    /**
     * Resets the <code>Popup</code> to an initial state.
     */
    void reset(Component owner, Component contents, int ownerX,
        int ownerY) {
      super.reset(owner, contents, ownerX, ownerY);

      Component component = getComponent();

      component.setLocation(ownerX, ownerY);
      rootPane.getContentPane().add(contents, BorderLayout.CENTER);
      contents.invalidate();
      component.validate();
      pack();
    }


    // This implements SwingHeavyWeight so that repaints on it
    // are processed by the RepaintManager and SwingPaintEventDispatcher.
    private static class MediumWeightComponent extends Panel implements
        SwingHeavyWeight {

      MediumWeightComponent() {
        super(new BorderLayout());
      }
    }
  }
}
